package com.umeox.moto.mqtt.event;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.umeox.moto.mqtt.BuildConfig;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

public class PersistentEventDispatcher {
    public static final int MSG_CONNECT_STATE_CONNECTED = 1;
    public static final int MSG_CONNECT_STATE_DISCONNECTED = 2;
    public static final int MSG_CONNECT_STATE_CONNECTFAIL = 3;
    public static final int MSG_ARRIVED = 4;
    public static final int MSG_SESSION_LOGGED_OUT = 5;
    public static final int MSG_SESSION_EFFECTIVE = 6;
    public static final int MSG_SESSION_INVALID = 7;

    private static final String TAG = PersistentEventDispatcher.class.getName();

    private HashMap<IOnPushListener, Boolean> pushListeners;
    private HashMap<IConnectionStateListener, Boolean> connStateListeners;
    private HashMap<INetSessionStateListener, Boolean> sessionStateListeners;
    private EventHandler eventHandler;

    private PersistentEventDispatcher() {
    }

    public static PersistentEventDispatcher getInstance() {
        return InstanceHolder.INSTANCE;
    }

    private void init() {
        if (this.eventHandler == null) {
            this.eventHandler = new EventHandler();
        }
    }

    public void registerOnPushListener(IOnPushListener listener, boolean delayed) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "registerOnPushListener()");
        }
        synchronized (this) {
            if (listener != null) {
                if (pushListeners == null) {
                    pushListeners = new HashMap<>();
                }
                pushListeners.put(listener, delayed);
            }
        }
    }

    public void unregisterOnPushListener(IOnPushListener listener) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "unregisterOnPushListener()");
        }
        synchronized (this) {
            if (listener != null && pushListeners != null && pushListeners.size() > 0) {
                pushListeners.remove(listener);
            }
        }
    }

    public void registerOnTunnelStateListener(IConnectionStateListener listener, boolean delayed) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "registerOnTunnelStateListener()");
        }
        synchronized (this) {
            if (listener != null) {
                if (connStateListeners == null) {
                    connStateListeners = new HashMap<>();
                }
                connStateListeners.put(listener, delayed);
            }
        }
    }

    public void unregisterOnTunnelStateListener(IConnectionStateListener listener) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "unregisterOnTunnelStateListener()");
        }
        synchronized (this) {
            if (listener != null && connStateListeners != null && connStateListeners.size() > 0) {
                connStateListeners.remove(listener);
            }
        }
    }

    public void registerNetSessionStateListener(INetSessionStateListener listener, boolean delayed) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "registerNetSessionStateListener()");
        }
        synchronized (this) {
            if (null != listener) {
                if (sessionStateListeners == null) {
                    sessionStateListeners = new HashMap<>();
                }
                sessionStateListeners.put(listener, delayed);
            }
        }
    }

    public void unregisterNetSessionStateListener(INetSessionStateListener listener) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "unregisterNetSessionStateListener()");
        }
        synchronized (this) {
            if (listener != null && sessionStateListeners != null && sessionStateListeners.size() > 0) {
                sessionStateListeners.remove(listener);
            }
        }
    }

    public void handlerMessage(int what) {
        handlerMessage(what, null, null, null);
    }

    public void handlerMessage(int what, String errorMsg) {
        handlerMessage(what, null, null, errorMsg);
    }

    public void handlerMessage(int what, String topic, byte[] payload) {
        handlerMessage(what, topic, payload, null);
    }

    private void handlerMessage(int what, String topic, byte[] payload, String errorMsg) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "handlerMessage()");
        }
        synchronized (this) {
            Set keys;
            Iterator it;
            switch (what) {
                case MSG_ARRIVED:
                    if (pushListeners != null) {
                        it = pushListeners.keySet().iterator();
                        while (it.hasNext()) {
                            IOnPushListener onPushListener = (IOnPushListener) it.next();
                            if (onPushListener.shouldHandle(topic)) {
                                if (pushListeners.get(onPushListener)) {
                                    eventHandler.sendDelayed(MSG_ARRIVED, onPushListener, topic, payload);
                                } else {
                                    onPushListener.onCommand(topic, payload);
                                }
                            }
                        }
                    }
                    break;
                case MSG_SESSION_EFFECTIVE:
                case MSG_SESSION_INVALID:
                case MSG_SESSION_LOGGED_OUT:
                    if (sessionStateListeners != null) {
                        keys = sessionStateListeners.keySet();
                        it = keys.iterator();
                        while (it.hasNext()) {
                            INetSessionStateListener listener = (INetSessionStateListener) it.next();
                            if (sessionStateListeners.get(listener)) {
                                eventHandler.sendDelayed(what, listener, null);
                            } else {
                                onSessionState(what, listener);
                            }
                        }
                    }
                    break;
                case MSG_CONNECT_STATE_CONNECTED:
                case MSG_CONNECT_STATE_DISCONNECTED:
                case MSG_CONNECT_STATE_CONNECTFAIL:
                    if (connStateListeners != null) {
                        keys = connStateListeners.keySet();
                        it = keys.iterator();
                        while (it.hasNext()) {
                            IConnectionStateListener stateListener = (IConnectionStateListener) it.next();
                            if (connStateListeners.get(stateListener)) {
                                eventHandler.sendDelayed(what, stateListener, errorMsg);
                            } else {
                                onTunnelState(what, stateListener, errorMsg);
                            }
                        }
                    }
                    break;
                default:
                    break;
            }
        }
    }

    private void onTunnelState(int state, IConnectionStateListener listener, String msg) {
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "OnTunnelState()");
        }
        if (listener != null) {
            try {
                if (state == MSG_CONNECT_STATE_CONNECTED) {
                    listener.onConnected();
                } else if (state == MSG_CONNECT_STATE_DISCONNECTED) {
                    listener.onDisconnect();
                } else if (state == MSG_CONNECT_STATE_CONNECTFAIL) {
                    listener.onConnectFail(msg);
                }
            } catch (Exception e) {
                if (BuildConfig.DEBUG) {
                    Log.e(TAG, "catch exception from IConnectionStateListener");
                }
            }
        }
    }

    private void onSessionState(int state, INetSessionStateListener listener) {
        if (listener != null) {
            try {
                if (state == MSG_SESSION_EFFECTIVE) {
                    listener.onSessionEffective();
                } else if (state == MSG_SESSION_INVALID) {
                    listener.onSessionInvalid();
                } else if (state == MSG_SESSION_LOGGED_OUT) {
                    listener.onNeedLogin();
                }
            } catch (Exception e) {
                if (BuildConfig.DEBUG) {
                    Log.e(TAG, "catch exception from INetSessionStateListener");
                }
            }
        }
    }

    private static class EventHandler extends Handler {
        EventHandler() {
            super(Looper.getMainLooper());
        }

        @Override
        public void handleMessage(Message msg) {
            if (msg != null && msg.obj instanceof MsgObject) {
                MsgObject obj = (MsgObject) msg.obj;
                if (obj.listener instanceof IOnPushListener) {
                    IOnPushListener listener = (IOnPushListener) obj.listener;
                    if (msg.what == MSG_ARRIVED) {
                        listener.onCommand(obj.topic, obj.payload);
                    }
                } else if (obj.listener instanceof IConnectionStateListener) {
                    PersistentEventDispatcher.getInstance().onTunnelState(msg.what, (IConnectionStateListener) obj.listener, obj.errorMsg);
                } else if (obj.listener instanceof INetSessionStateListener) {
                    PersistentEventDispatcher.getInstance().onSessionState(msg.what, (INetSessionStateListener) obj.listener);
                }
            }
        }

        void sendDelayed(int what, Object listener, String errorMsg) {
            Message msg = this.obtainMessage();
            msg.what = what;
            msg.obj = new MsgObject(listener, errorMsg);
            sendMessageDelayed(msg, 10L);
        }

        void sendDelayed(int what, Object listener, String topic, byte[] payload) {
            Message msg = this.obtainMessage();
            msg.what = what;
            msg.obj = new MsgObject(listener, topic, payload);
            sendMessageDelayed(msg, 10L);
        }

        private static class MsgObject {
            Object listener;
            String errorMsg;
            String topic;
            byte[] payload;

            MsgObject(Object listener, String errorMsg) {
                this.listener = listener;
                this.errorMsg = errorMsg;
            }

            MsgObject(Object listener, String topic, byte[] payload) {
                this.listener = listener;
                this.topic = topic;
                this.payload = payload;
            }
        }
    }

    private static class InstanceHolder {
        private static final PersistentEventDispatcher INSTANCE = new PersistentEventDispatcher();

        static {
            INSTANCE.init();
        }
    }
}
